﻿using System.Diagnostics;
using TakePhotoTogether.Communication.Protocols;

namespace TakePhotoTogether.Communication
{
    /// <summary>
    /// Type of Messages that can be transmitted using Packets
    /// </summary>
    public enum MessageTypes
    {
        /// <summary> Packet that warns remote device about whether device is on background state </summary>
        BackgroundStreamPacket = 1,
        ///<summary> Packet that represents the action of close the app for some person</summary>
        CloseAppPacket = 2,
        /// <summary> Packet that warns remote user that device is on LowBattery state and must close connection </summary>
        LowBatteryPacket = 3,
        // <summary> Packet to warns that photo was taken </summary>
        TakePhotoPacket = 4,
        /// <summary> Packet to measure network delay </summary>
        RoundTripTimePacket = 5,
        /// <summary> Packet to communciation any change to view (Full or Split mode)</summary>
        ViewStatePacket = 6,
        /// <summary> Packet that handles distributed locking communication </summary>
        ResourceLockingPacket = 7,
        /// <summary> Packet that communicates the user identity </summary>
        IdentificationPacket = 8,
        /// <summary> Packet to send or receive an image to or from remote device </summary>
        StreamPacket = 9,
        /// <summary> Sync all pictures taken</summary>
        SyncPacket = 10,
        /// <summary> Packet that represents a greater packets sliced into smaller ones</summary>
        SlicedPacket = 11,
        /// <summary> Packet that represents ip address</summary>
        IPAddressPacket = 12,
        /// <summary> Packet that confirms photo was synced </summary>
        SyncPhotoACKPacket = 13
    }

    /// <summary>
    /// Factory class that handles transparently packet creation
    /// </summary>
    public class DataPacketFactory
    {
        // Max level of priority of each packet
        public const int MAX_PRIORITY_LEVEL = 13;
        private static readonly int[] weightByPriority = new int[MAX_PRIORITY_LEVEL]
        {
            1,          // BackgroundStream
            1,          // CloseApp
            1,          // LowBattery
            1,          // TakePhotoPacket
            1,          // RoundTripTimePacket
            5,          // ViewStatePacket
            1,          // ResourceLockingPacket
            1,          // IdentificationPacket
            70,         // StreamPacket
            20,         // SyncPacket
            1,          // SlicedPacket
            1,          // IPAddress
            1
        };


        /// <summary>
        /// Get Payload class from message type
        /// </summary>
        /// <param name="messageType">Integer that must correspond to one of the MessageTypes enums</param>
        /// <param name="data">byte array that represents Payload </param>
        /// <returns>A concrete realization of PayloadData</returns>
        public static PayloadData GetPayload(int messageType, byte[] data)
        {
            PayloadData payload = null;

            if (messageType == (int)MessageTypes.StreamPacket)
            {
                payload = new StreamData(data);
            }
            else if (messageType == (int)MessageTypes.SyncPacket)
            {
                payload = new SyncData(data);
            }
            else if (messageType == (int)MessageTypes.ViewStatePacket)
            {
                payload = new ViewStateData(data);
            }
            else if (messageType == (int)MessageTypes.SlicedPacket)
            {
                payload = new SlicedData(data);
            }
            else if (messageType == (int)MessageTypes.ResourceLockingPacket)
            {
                payload = new LockingData(data);
            }
            else if (messageType == (int)MessageTypes.IdentificationPacket)
            {
                payload = new IdentificationData(data);
            }
            else if (messageType == (int)MessageTypes.IPAddressPacket)
            {
                payload = new IPAddressData(data);
            }
            else if (messageType == (int)MessageTypes.RoundTripTimePacket)
            {
                payload = new RoundTripTimeData(data);
            }
            else if (messageType == (int)MessageTypes.TakePhotoPacket)
            {
                payload = new TakePhotoData(data);
            }
            else if (messageType == (int)MessageTypes.SyncPhotoACKPacket)
            {
                payload = new SyncPhotoACKData(data);
            }
            else if (messageType == (int)MessageTypes.CloseAppPacket) 
            {
                payload = new CloseAppData(data);
            }
            else if (messageType == (int)MessageTypes.BackgroundStreamPacket) 
            {
                payload = new BackgroundStreamData(data);
            }
            else if (messageType == (int)MessageTypes.LowBatteryPacket)
            {
                payload = new LowBatteryData(data);
            }
                
            return payload;
        }

        /// <summary>
        /// Get priority of packet in order to be better schedulled inside DataQueue.
        /// Priority is defined as 
        /// </summary>
        /// <param name="packet"></param>
        /// <returns>An integer representing a priority. Smaller int value means a greater pr</returns>
        public static int GetPriority(DataPacket packet)
        {
            Debug.Assert(packet != null, "Can't get priority from a null packet");

            int priority = (int)packet.MessageType;

            if (priority == (int)MessageTypes.SlicedPacket && packet.Payload != null && packet.Payload is SlicedData)
            {
                priority = ((SlicedData)(packet.Payload)).DataType;
            }

            return priority;
        }

        /// <summary>
        /// Get bandwidth related to the packet type
        /// </summary>
        /// <param name="packet">packet in which is deduced the message type and the related bandwidth</param>
        /// <returns></returns>
        public static int GetPacketBandwidth(DataPacket packet)
        {
            return GetPacketBandwidth(GetPriority(packet));
        }

        /// <summary>
        /// Get bandwidth related to the packet type
        /// </summary>
        /// <param name="priority">packet priority from which is deduced the message type and the related bandwidth</param>
        /// <returns>integer representing a bandwidth</returns>
        public static int GetPacketBandwidth(int priority)
        {
            Debug.Assert(priority > 0 && priority <= MAX_PRIORITY_LEVEL, "Packet with wrong priority!");

            return weightByPriority[priority - 1];
        }

        /// <summary>
        /// Create a Stream packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetStreamPacket(byte[] imageBytes, int width, int height)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.StreamPacket;
            packet.Payload = new StreamData(imageBytes, width, height);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a Stream packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetRoundTripTimePacket(bool travel = false)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.RoundTripTimePacket;
            packet.Payload = new RoundTripTimeData(travel);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Get packet to warn remote device that a photo was taken
        /// </summary>
        /// <returns></returns>
        public static DataPacket GetTakePhotoData(string filename)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.TakePhotoPacket;
            packet.Payload = new TakePhotoData(filename);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Get packet to warn remote device that a photo was taken
        /// </summary>
        /// <returns></returns>
        public static DataPacket GetSyncPhotoACKData(string filename)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.SyncPhotoACKPacket;
            packet.Payload = new SyncPhotoACKData(filename);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        public static DataPacket GetRoundTripTimePacket(RoundTripTimeData rttData)
        {
            rttData.returningTravel = true;

            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.RoundTripTimePacket;
            packet.Payload = rttData;
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a Sync packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetSyncPacket(byte[] imageBytes, int width, int height, string filename)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.SyncPacket;
            packet.Payload = new SyncData(imageBytes, width, height, filename);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a Locking packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetResourceLockingPacket(LockingMessage message)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.ResourceLockingPacket;
            packet.Payload = new LockingData(message);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a Locking packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetIdentificationPacket(string identification)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.IdentificationPacket;
            packet.Payload = new IdentificationData(identification);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a Close packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetCloseAppPacket()
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.CloseAppPacket;
            packet.Payload = new CloseAppData("C");
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a LowBattery packet from a image
        /// </summary>
        /// <param name="image">image to be packeted</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetLowBatteryPacket()
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.LowBatteryPacket;
            packet.Payload = new LowBatteryData();
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }

        /// <summary>
        /// Create a BackgroundStream packet from a image
        /// </summary>
        /// <param name="active">if player is on background</param>
        /// <returns>DataPacket structure to be sent to remote</returns>
        public static DataPacket GetBackgroundStreamPacket(bool active)
        {
            DataPacket packet = new DataPacket();
            packet.MessageType = (int)MessageTypes.BackgroundStreamPacket;
            packet.Payload = new BackgroundStreamData(active);
            packet.DataSize = packet.Payload.ToBytes().Length;

            return packet;
        }
    }
}
